#include <openssl/err.h>

#include "./SslContext.h"
#include "../../../tcp/code/utility/Logger.h"

bool SslContext::init()
{
    ::OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS |
                           OPENSSL_INIT_LOAD_CRYPTO_STRINGS,
                       nullptr); // 加载SSL错误字符串表；加载加密库错误字符串表；保留

    const SSL_METHOD *method = ::TLS_server_method(); // 服务端的操作方法配置
    this->ctx_ = ::SSL_CTX_new(method);
    if (!this->ctx_)
    {
        this->handleError("Failed to create SSL context");
        return false;
    }

    long options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
                   SSL_OP_NO_COMPRESSION |
                   SSL_OP_CIPHER_SERVER_PREFERENCE; // 服务器端加密套件优先级列表优先于客户端
    ::SSL_CTX_set_options(this->ctx_, options);

    if (!this->loadCertificatesAndPrivateKey())
    {
        return false;
    }

    if (!this->setupProtocolVersionAndCipherList())
    {
        return false;
    }

    this->setupSessionCacheSizeAndTimeout();

    LOG_INFO("SSL context initialized successfully");
    return true;
}

void SslContext::handleError(const char *msg)
{
    char buf[256]{};
    ::ERR_error_string_n(::ERR_get_error(), buf, sizeof(buf));
    LOG_ERROR("%s: %s", msg, buf);
}

bool SslContext::loadCertificatesAndPrivateKey()
{
    // 证书
    if (::SSL_CTX_use_certificate_file(this->ctx_,
                                       this->config_.getCertificateFile().c_str(), SSL_FILETYPE_PEM) <= 0)
    {
        this->handleError("Failed to load server certificate");
        return false;
    }

    // 私钥
    if (::SSL_CTX_use_PrivateKey_file(this->ctx_,
                                      this->config_.getPrivateKeyFile().c_str(), SSL_FILETYPE_PEM) <= 0)
    {
        this->handleError("Failed to load private key");
        return false;
    }
    if (!::SSL_CTX_check_private_key(this->ctx_))
    {
        this->handleError("Private key does not match the certificate");
        return false;
    }

    // 证书链
    if (!this->config_.getCertificateChainFile().empty())
    {
        if (::SSL_CTX_use_certificate_chain_file(this->ctx_,
                                                 this->config_.getCertificateChainFile().c_str()) <= 0)
        {
            this->handleError("Failed to load certificate chain");
            return false;
        }
    }

    return true;
}

bool SslContext::setupProtocolVersionAndCipherList()
{
    // 默认禁用所有版本
    long options = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_3;
    switch (this->config_.getProtocolVersion())
    {
    case SslVersion::Ssl10:
        options &= ~SSL_OP_NO_TLSv1; // 启用
        break;
    case SslVersion::Ssl11:
        options &= ~SSL_OP_NO_TLSv1_1;
        break;
    case SslVersion::Ssl12:
        options &= ~SSL_OP_NO_TLSv1_2;
        break;
    case SslVersion::Ssl13:
        options &= ~SSL_OP_NO_TLSv1_3;
        break;
    }
    ::SSL_CTX_set_options(this->ctx_, options);

    if (!this->config_.getCipherList().empty())
    {
        if (::SSL_CTX_set_cipher_list(this->ctx_,
                                      this->config_.getCipherList().c_str()) <= 0)
        {
            this->handleError("Failed to set cipher list");
            return false;
        }
    }

    return true;
}

void SslContext::setupSessionCacheSizeAndTimeout()
{
    SSL_CTX_set_session_cache_mode(this->ctx_, SSL_SESS_CACHE_SERVER);
    SSL_CTX_sess_set_cache_size(this->ctx_, this->config_.getSessionCacheSize());
    ::SSL_CTX_set_timeout(this->ctx_, this->config_.getSessionTimeout());
}